#pragma once

#include <cstdint>
#include "gateway_framework.hh"
#include "../../corelib/singleton.hpp"
#include "../../corelib/network/mem_pool.hh"
#include "../../corelib/util/allocator.hh"

// The client that wrapped the remote session
class Client {
private:
    SessionID sessionID_; // The session ID
    // The unsent packet
    struct UnsentPacket {
        UnsentPacket(const UnsentPacket&) = delete;
        const UnsentPacket& operator=(const UnsentPacket&) = delete;
        char* msg; // message data address
        std::size_t length; // the length of data
        // ctor
        UnsentPacket() {
            msg = nullptr;
            length = 0;
        }
        // ctor
        // @param data data address
        // @param len data length
        UnsentPacket(const char* data, std::size_t len) {
            msg = MempoolRef.allocate(len);
            memcpy(msg, data, len);
            length = len;
        }
        // rvalue copy ctor
        UnsentPacket(UnsentPacket&& rht) {
            msg = rht.msg;
            length = rht.length;
            rht.msg = nullptr;
            rht.length = 0;
        }
        // dtor
        ~UnsentPacket() {
            if (msg) {
                MempoolRef.dispose(msg);
            }
            msg = nullptr;
        }
    };
    using PacketList = std::list<UnsentPacket>;
    // The path Info.
    struct PathInfo {
        PathID pathID; // path ID
        PacketList UnsentPackets; // unsent packets
        bool finding; // Is finding the path?
    };
    using PathMap = UNORDERED_MAP(SiteType, PathInfo, Client_pathMap_);
    PathMap pathMap_; // path map
    bool dtor_; // under destructing

public:
    // ctor
    // @param sessionID session ID
    Client(SessionID sessionID);
    // dtor
    ~Client();
    // The message from client
    // @param msgID message ID
    // @param msg message data address
    // @param length the message length
    void fromClient(std::uint32_t msgID, const char* msg, std::size_t length);
    // The message from engine
    // @param msg message data address
    // @param length the message length
    void toClient(const char* msg, std::size_t length);
    // The message from engine
    // @param msg protobuf::Message
    void toClient(const Message& msg);
    // A new path has been created
    // @param pathID path ID
    // @param siteType grid site type
    void newPath(PathID pathID, SiteType siteType);
    // The path has been broken
    // @param pathID path ID
    // @param siteType grid site type
    void removePath(PathID pathID, SiteType siteType);
    // Returns session ID
    SessionID getSessionID();

private:
    // Flush all unsent packet to path
    void flush(PathInfo* pathInfo);
};

// The client manager
class ClientManager {
public:
    // ctor
    ClientManager();
    // dtor
    ~ClientManager();
    // Create a new client
    // @param sessionID client session ID
    // @retval true
    // @retval false fail
    bool add(SessionID sessionID);
    // Remove(destroy) a client
    // @param sessionID client session ID
    // @retval true
    // @retval false fail
    bool remove(SessionID sessionID);
    // Query client
    // @param sessionID session ID
    // @retval nullptr not found
    // @retval Client pointer
    Client* get(SessionID sessionID);
    // Broadcast
    // @param msg protobuf::Message
    void broadcast(const Message& msg);
    // Returns client count
    std::size_t size();

private:
    using ClientMap = UNORDERED_MAP(SessionID, Client*, ClientManager_clientMap_);
    ClientMap clientMap_; // The client map
};

#define ClientManagerRef (*kratos::corelib::Singleton<ClientManager>::instance())
